package com.md.config.nacos;

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.parser.NacosByteArrayResource;
import com.alibaba.cloud.nacos.utils.NacosConfigUtils;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.md.base.exception.ServerException;
import com.md.value.enums.ErrorEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @author 刘骄阳
 * 2022-07-19 11:33
 */
@Component
@Slf4j
public final class NacosConfigLocalCatch implements InitializingBean {
  private static final String DEFAULT_EXTENSION = "properties";
  private static final String DOT = ".";
  private static final List<PropertySourceLoader> PROPERTY_SOURCE_LOADERS = Lists.newArrayList();
  private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
    2, 4, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100),
    new ThreadPoolExecutor.CallerRunsPolicy()
  );
  private final Map<String, Map<String, Object>> localCatchMap = new HashMap<>();
  @Resource
  private NacosConfigProperties nacosConfigProperties;
  /**
   * JsonIgnore: 标注序列化的时候, 不返回该字段
   */
  @Resource
  @JsonIgnore
  private Environment environment;

  private NacosConfigLocalCatch() {
    PROPERTY_SOURCE_LOADERS.addAll(SpringFactoriesLoader
      .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()));
  }

  /**
   * 加载完其他bean的时候执行,进行配置和最终初始化。
   */
  @Override
  public void afterPropertiesSet() {
    String name = environment.getProperty("spring.application.name");
    NacosConfig nacosConfigInfo = new NacosConfig(nacosConfigProperties.getServerAddr(),
      nacosConfigProperties.getNamespace(), nacosConfigProperties.getGroup(),
      name, true, String.class, nacosConfigProperties.getFileExtension());
    this.listener(nacosConfigInfo);
    log.info(name);
  }

  public void listener(final NacosConfig nacosConfig) {
    Listener listener = new Listener() {
      /**
       * 监听-工作线程池
       */
      @Override
      public Executor getExecutor() {
        return THREAD_POOL_EXECUTOR;
      }

      /**
       * 监听到nacos配置变更
       *
       * @param context 监听到修改后的配置
       */
      @Override
      public void receiveConfigInfo(final String context) {
        Map<String, Object> updateAfter = parseConfig(nacosConfig, context);
        Map<String, Object> updateBefore = localCatchMap.get(nacosConfig.getDataId());
        compile(nacosConfig, updateAfter);

        updateBefore.forEach((key, value) -> {
          Object updateValue = updateAfter.get(key);
          updateAfter.remove(key);
          if (!value.equals(updateValue) && updateValue != null) {
            log.info("修改前key: {},value: {};", key, value);
            log.info("修改后key: {},value: {};", key, updateValue);
          }
        });
        updateAfter.forEach((key, value) ->
          log.info("新增key: {},value: {};", key, value));
      }
    };


    ConfigService configService = this.getConfigService(nacosConfig);
    try {
      String config = configService.getConfig(nacosConfig.getDataId(), nacosConfig.getGroup(),
        nacosConfig.getTimeout());
      Map<String, Object> map = parseConfig(nacosConfig, config);
      // 初始化
      compile(nacosConfig, map);
      configService.addListener(nacosConfig.getDataId(), nacosConfig.getGroup(), listener);
    } catch (NacosException e) {
      e.printStackTrace();
      log.error("配置中心 监听异常! dataId = {}", nacosConfig.getDataId());
    }
  }

  private void compile(final NacosConfig nacosConfig, final Map<String, Object> config) {
    localCatchMap.put(nacosConfig.getDataId(), config);
  }

  /**
   * 获取ConfigService
   *
   * @return ConfigService
   */
  private ConfigService getConfigService(final NacosConfig nacosConfig) {
    String serverAddr = nacosConfig.getServerAddress();
    String nameSpace = nacosConfig.getNamespace();
    Properties properties = new Properties();
    properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
    properties.put(PropertyKeyConst.NAMESPACE, nameSpace);
    ConfigService configService;
    try {
      configService = NacosFactory.createConfigService(properties);
    } catch (NacosException e) {
      log.error("Nacos config 配置异常: {}", e.getMessage(), e);
      throw ServerException.build(ErrorEnum.SYSTEM_ERROR);
    }
    return configService;
  }

  /**
   * 解析配置
   *
   * @param nacosConfig 配置参数
   * @param config      配置
   * @return map
   */
  private Map<String, Object> parseConfig(final NacosConfig nacosConfig, final String config) {
    try {
      return parseData(nacosConfig.getDataId(), config, nacosConfig.getFileExtension());
    } catch (IOException e) {
      log.error("解析配置出错: {}", e.getMessage());
    }
    return Maps.newHashMap();
  }

  public Map<String, Object> parseData(final String configName, final String configValue,
                                       final String extension) throws IOException {
    Map<String, Object> map = Maps.newHashMap();
    // 判断配置是否为空
    if (StringUtils.isEmpty(configValue)) {
      return map;
    }
    // 判断后缀是否为空
    String prefix;
    if (StringUtils.isEmpty(extension)) {
      // 为空使用默认的后缀
      prefix = this.getFileExtension(configName);
    } else {
      prefix = extension;
    }
    List<PropertySource<?>> propertySourceList = Lists.newArrayList();

    for (PropertySourceLoader propertySourceLoader : PROPERTY_SOURCE_LOADERS) {
      // 循环判断后缀是否是指定的后缀
      if (!canLoadFileExtension(propertySourceLoader, extension)) {
        continue;
      }
      NacosByteArrayResource nacosByteArrayResource;
      if (propertySourceLoader instanceof PropertiesPropertySourceLoader) {
        nacosByteArrayResource = new NacosByteArrayResource(
          NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(),
          configName);
      } else {
        nacosByteArrayResource = new NacosByteArrayResource(
          configValue.getBytes(), configName);
      }
      propertySourceList = propertySourceLoader
        .load(configName, nacosByteArrayResource);
    }

    if (CollectionUtils.isEmpty(propertySourceList)) {
      return map;
    }
    propertySourceList.forEach(propertySource -> {
      if (propertySource instanceof EnumerablePropertySource) {
        String[] propertyNames = ((EnumerablePropertySource<?>) propertySource)
          .getPropertyNames();
        int length = propertyNames.length;
        if (length > 0) {
          for (String name : propertyNames) {
            map.put(name, propertySource.getProperty(name));
          }
        }
      }
    });
    return map;
  }

  /**
   * 获取名字的后缀
   *
   * @param name 名字
   * @return 后缀
   */
  public String getFileExtension(final String name) {
    if (StringUtils.isEmpty(name)) {
      // 为空则使用默认的后缀
      return DEFAULT_EXTENSION;
    }
    int idx = name.lastIndexOf(DOT);
    if (idx > 0 && idx < name.length() - 1) {
      return name.substring(idx + 1);
    }
    // 不存在则使用默认的后缀
    return DEFAULT_EXTENSION;
  }

  /**
   * 判断是否以指定后缀结尾
   *
   * @param loader    后缀范围
   * @param extension 指定后缀
   * @return true/false
   */
  private boolean canLoadFileExtension(final PropertySourceLoader loader, final String extension) {
    String[] fileExtensions = loader.getFileExtensions();
    for (String fileExtension : fileExtensions) {
      // 判断是否以指定后缀结尾
      if (StringUtils.endsWithIgnoreCase(extension, fileExtension)) {
        return true;
      }
    }
    return false;
  }
}
